/*
 * Parser.java
 *
 * Copyright (C) 2013 UPOU.
 * IS214 - Programming Languages
 * All Rights Reserved.
 *
 * @author Mary Joy Pascual and Simon Zagado
 */

package com.upittc.flopol.core;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;

import com.upittc.flopol.constant.ErrorConstant;
import com.upittc.flopol.constant.FlopolConstant;
import com.upittc.flopol.core.beans.Symbol;
import com.upittc.flopol.core.helper.CoreHelper;

public class Parser {

	public Map <String, Symbol> symtbl = null;
	private Stack <String> stack; 
	public Executor executor = null;
	public Lexer lex = null;
	public Lexer loopLex = null;
	public Symbol symbol = null;
	public String lexeme = null;
	public String loopTokens = null;
	public int nextToken = 0;
	public int lineNo = 0;
	public int op = 0;
	public boolean fl = false;
	public boolean ll = false;
	
	public Parser(Lexer lexer) throws Exception{ 
		this.lex = lexer;
		lexer();
		symtbl = new LinkedHashMap<String, Symbol>();
		executor = new Executor();
		stack = new Stack<String>();
		SemanticAnalyzer.initSemanticAnalyzer(symtbl);
	}

	public void lexer() throws Exception {
		if(ll == true){
			loopLex.lexer();
			nextToken = loopLex.getNextToken().getToken();
			lexeme = loopLex.getNextToken().getLexeme();
		} else {
			lex.lexer();
			nextToken = lex.getNextToken().getToken();
			lexeme = lex.getNextToken().getLexeme();
			lineNo = lex.getNextToken().getLineNo();
		}
	}

	// <code>::=FLOP <create> <maneuver> FLOP
	public void code() throws Exception {
		if (nextToken == FlopolConstant.FLOP_KEY) {
			lexer();
			create();
			maneuver();
			if (nextToken == FlopolConstant.FLOP_KEY) {
				lexer();
				if(nextToken == FlopolConstant.EOFF){
					CoreHelper.give(FlopolConstant.SUCCESS);
				}  else {
					throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_CHAR, lineNo));
				}
			} else {
				throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_FLOP, lineNo));
			}
		} else
			throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_FLOP, lineNo));
	}

	// <create> ::= CREATE {<declaration>} END
	public void create() throws Exception {
		if (nextToken == FlopolConstant.CREATE_KEY) {
			lexer();
			do {
				declaration();
			} while(nextToken != FlopolConstant.END_KEY);
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_CREATE, lineNo));
		}
	}

	// <declaration> ::= <data_type> <identifier> IS <expression>
	public void declaration()throws Exception {
		dataType();
		identifier();
		if( nextToken == FlopolConstant.IS_KEY){
			lexer();
			expression();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_IS, lineNo));
		}
		executeCreate();
		op = FlopolConstant.GLOBAL_VAR;
	}

	// <expression> ::= <num_exp> | <rel_exp> | <log_exp> | <value>
	public void expression() throws Exception {
		if(nextToken == FlopolConstant.NUM_OP || nextToken == FlopolConstant.UNARY_OP){
			op = FlopolConstant.NUM_OP;
			num_exp();
		} else if (nextToken == FlopolConstant.REL_OP){
			op = FlopolConstant.REL_OP;
			rel_exp();
		} else if (nextToken == FlopolConstant.EQNEQ_OP){ 
			op = FlopolConstant.EQNEQ_OP;
			rel_exp();
		} else if (nextToken == FlopolConstant.LOG_OP || nextToken == FlopolConstant.NOT_OP){
			op = FlopolConstant.LOG_OP;
			log_exp();
		}  else {
			SemanticAnalyzer.checkValue(symbol, nextToken, lineNo, lexeme, op);
			value();
		}
	}
	
	// <num_exp> ::= <num_op> <expression> <expression> | PING <expression>
	public void num_exp() throws Exception{
		if(nextToken == FlopolConstant.UNARY_OP){
			stack.push(lexeme);
			lexer();
			expression();
		} else if (nextToken == FlopolConstant.NUM_OP){
			num_op();
			expression();
			expression();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_OPER, lineNo));
		}
	}
	
	// <rel_exp> ::= <rel_op> <expression> <expression> 
	private void rel_exp() throws Exception{
		rel_op();
		expression();
		expression();
	}
	
	// <log_exp> ::= <log_op> <expression> <expression> | NOT <expression>
	private void log_exp()throws Exception{
		if(nextToken == FlopolConstant.NOT_OP){
			stack.push(lexeme);
			lexer();
			expression();
		} else if (nextToken == FlopolConstant.LOG_OP){
			log_op();
			expression();
			expression();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_OPER, lineNo));
		}
	}
	
	// <maneuver>  ::= 	MANEUVER {<statement>} END
	public void maneuver() throws Exception {
		if (nextToken == FlopolConstant.MAN_KEY) {
			lexer();
			do {
				statement();
			} while(nextToken != FlopolConstant.END_KEY);
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_MANEUVER, lineNo));
		}
	}
	
	// <statement> ::= <assignment>  | <inp_statement> | <out_statement> | <if_statement> | <loop_statement>
	public void statement() throws Exception{
		op = FlopolConstant.ASSIGN_VAR;
		if (nextToken == FlopolConstant.INTO_KEY){
			assignment();
		} else if (nextToken == FlopolConstant.BEG_KEY){
			inp_statement();
		} else if (nextToken == FlopolConstant.GIVE_KEY || nextToken == FlopolConstant.GVMR_KEY){
			out_statment();
		} else if (nextToken == FlopolConstant.CHECK_KEY){
			fl = false;
			if_statement();
		} else if (nextToken == FlopolConstant.PERFORM_KEY){
			loop_statement();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_STATE, lineNo));
		}
		stack = new Stack<String>();
	}

	// <assignment> ::= INTO <identifier> IS <expression>
	private void assignment()throws Exception {
		if (nextToken == FlopolConstant.INTO_KEY){
			lexer();
			if(nextToken == FlopolConstant.IDENT){
				executeAssignSymbol(lexeme, nextToken);
				lexer();
				if(nextToken == FlopolConstant.IS_KEY){
					lexer();
					expression();
				} else {
					throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_IS, lineNo));
				}
			} else {
				throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_IDENT, lineNo));
			}
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_STATE, lineNo));
		}
		executeCreate();
	}

	// <inp_statement> ::= BEG < identifier >
	private void inp_statement() throws Exception{
		if (nextToken == FlopolConstant.BEG_KEY){
			lexer();
			if(nextToken == FlopolConstant.IDENT){
				executeBeg(nextToken, lineNo, lexeme, op);
				lexer();
			} else {
				throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_IDENT, lineNo));
			}
		}else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_STATE, lineNo));
		}
	}

	// <out_statement> 	::=  GIVE <expression> | GIVEMORE <expression> 
	private void out_statment() throws Exception {
		int type = nextToken;
		if (nextToken == FlopolConstant.GIVE_KEY || nextToken == FlopolConstant.GVMR_KEY){ 
			lexer();
			executeAssignSymbol(lexeme, nextToken);
			expression();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_STATE, lineNo));
		}
		executor.executePrint(type, stack);
	}

	// <if_statement>	::=	CHECK <expression> TO [<statement>] |  CHECK<expression> TO [<statement>]  BUT  [<statement>] 
	private void if_statement() throws Exception{
		if (nextToken == FlopolConstant.CHECK_KEY){ 
			lexer();
			expression();
			if(nextToken == FlopolConstant.TO_KEY){
				lexer();
				if(nextToken == FlopolConstant.L_BR){
					lexer();
					if(executeCheck()){
						while (nextToken != FlopolConstant.R_BR){
							statement();
						}
						if(nextToken == FlopolConstant.R_BR){ 
							lexer();
						} else {
							throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_RBR, lineNo));
						}
					} else {
						fl = false;
						executeNext();
						if(nextToken == FlopolConstant.BUT_KEY){
							lexer();
							if(nextToken == FlopolConstant.L_BR){
								lexer();
								while (nextToken != FlopolConstant.R_BR){
									statement();
								}
								if(nextToken == FlopolConstant.R_BR){
									lexer();
								} else {
									throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_RBR, lineNo));
								}
							} else {
								throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_LBR, lineNo));
							}
						}
					}
					if(fl==true || nextToken == FlopolConstant.BUT_KEY){
						executeNext();
					}
				} else {
					throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_LBR, lineNo));
				}
			} else {
				throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_TO, lineNo));
			}
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_STATE, lineNo));
		}
	}

	// <loop_statement>	::=	PERFORM <expression> TIMES [<statement>]
	private void loop_statement()throws Exception {
		if(nextToken == FlopolConstant.PERFORM_KEY){
			lexer();
			expression();
			float times = executePerform();
			if(nextToken == FlopolConstant.TIMES_KEY){
				lexer();
				if(nextToken == FlopolConstant.L_BR){
					lexer();
					executeLoopStatement(times);
					lexer();
					for(float i=0; i<times; i++){
						while (nextToken != FlopolConstant.EOFF){
							statement();
						}
					}
					ll = false;
					lexer();
				} else {
					throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_LBR, lineNo));
				}
			} else {
				throw new Exception(CoreHelper.setError(ErrorConstant.MISSING_TIMES, lineNo));
			}
		} else {
			throw new Exception(CoreHelper.setErrorWoLine(ErrorConstant.INVALID_STATE));
		}
	}
	
	// <data_type> ::= FLOAT | STRING | BOOL
	public void dataType() throws Exception{
		if(nextToken == FlopolConstant.FLT_KEY || nextToken == FlopolConstant.BOL_KEY ||
		   nextToken == FlopolConstant.STR_KEY ){
			symbol = new Symbol();
			symbol.setVarType(lexeme);
			lexer();
		}else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_DATA, lineNo));
		}
	}
	
	// <rel_op> ::= GT? | GTE? | LT? | LTE? | EQ? | NEQ?
	private void rel_op() throws Exception{
		if (nextToken == FlopolConstant.REL_OP || nextToken == FlopolConstant.EQNEQ_OP){
			stack.push(lexeme);
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_REL, lineNo));
		}
	}
	
	// <num_op> ::=  ADD | SUB | MULT | DIVI | EXP | REMI   	
	private void num_op() throws Exception{
		if(nextToken == FlopolConstant.NUM_OP){
			stack.push(lexeme);
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_OPER, lineNo));
		}
	}
	
	// <log_op> ::= INTER | UNION | NON
	private void log_op() throws Exception {
		if (nextToken == FlopolConstant.LOG_OP){
			stack.push(lexeme);
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_LOG, lineNo));
		}
	}

	// <value> ::= <float_lit> | <str_lit> | <bool_lit> | <identifier>  
	private void value() throws Exception{
		if (nextToken == FlopolConstant.BOL_LIT|| nextToken == FlopolConstant.STR_LIT || 
			nextToken == FlopolConstant.FLT_LIT || nextToken == FlopolConstant.IDENT){
			if(nextToken == FlopolConstant.IDENT){
				Symbol sym = symtbl.get(lexeme);
				stack.push(sym.getVarVal());
			} else {
				stack.push(lexeme);
			}
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_EXP, lineNo));
		}
	}
	
	// <identifier> ::= <letter>{<letter>}
	public void identifier() throws Exception {
		if(nextToken == FlopolConstant.IDENT){
			symbol.setVarName(lexeme);
			lexer();
		} else {
			throw new Exception(CoreHelper.setError(ErrorConstant.INVALID_IDENT, lineNo));
		} 
	}
	
	
	/******************************EXECUTION**********************************/
	
	private void executeCreate() throws Exception{
		symbol.setVarVal(executor.getValue(stack));
		symtbl.put(symbol.getVarName(), symbol);
		symbol = new Symbol();
		stack = new Stack<String>();
		CoreHelper.print(symtbl);
	}
	
	private void executeBeg(int nextToken, int lineNo, String lexeme, int op) throws Exception{
		executeAssignSymbol(lexeme, nextToken);
		symbol.setVarVal(CoreHelper.beg(lexeme));
		SemanticAnalyzer.checkValueInput(symbol.getVarVal(), CoreHelper.tokenVal(symbol.getVarType()), lineNo);
		symtbl.put(symbol.getVarName(), symbol);
		symbol = new Symbol();
		stack = new Stack<String>();
		CoreHelper.print(symtbl);
	}
	private void executeAssignSymbol(String lexeme, int nextToken) throws Exception{
		if(nextToken == FlopolConstant.IDENT){
			symbol.setVarName(lexeme);
			Symbol sym = symtbl.get(symbol.getVarName());
			if(sym != null){
				symbol.setVarType(sym.getVarType());
			}
		} else if (nextToken == FlopolConstant.FLT_LIT || nextToken == FlopolConstant.BOL_LIT ||
				   nextToken == FlopolConstant.STR_LIT){
			symbol.setVarType(CoreHelper.tokenVal(nextToken));
		}
	}

	public boolean executeCheck() throws Exception{
		String val = executor.getValue(stack);
		SemanticAnalyzer.checkBool(val);
		stack = new Stack<String>();
		if(val.equals(FlopolConstant.YEAH)){
			return true;
		}
		return false;	
	}
	
	public float executePerform() throws Exception{
		String val = executor.getValue(stack);
		float numberTimes = SemanticAnalyzer.checkAndGetFloat(val);
		return numberTimes;	
	}
	
	public void executeLoopStatement(float times) throws Exception{
		loopTokens = "";
		String val = "";
		while (nextToken != FlopolConstant.R_BR){
			SemanticAnalyzer.checkEOF(nextToken);
			if(nextToken == FlopolConstant.L_BR){
				while (nextToken != FlopolConstant.R_BR){
					SemanticAnalyzer.checkEOF(nextToken);
					val = val + lexeme+ " "; 
					lexer();
				}
			}
			val = val + lexeme+ " "; 
			lexer();
		}
		for (float i=0; i<times; i++){
			loopTokens = loopTokens + val;
		}
		loopTokens = loopTokens + FlopolConstant.EOF;
		loopLex = new Lexer(loopTokens);
		ll = true;
	}
	
	public void executeNext() throws Exception{
		while (nextToken != FlopolConstant.R_BR){
			SemanticAnalyzer.checkEOF(nextToken);
			if(nextToken == FlopolConstant.L_BR){
				while (nextToken != FlopolConstant.R_BR){
					SemanticAnalyzer.checkEOF(nextToken);
					lexer();
				}
			}
			lexer();
		}
		lexer();
	}

}
